package com.demo.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.basic.domain.PageResult;
import com.basic.exception.WaitingLockDBException;
import com.basic.util.ConverterUtil;
import com.basic.util.QueryHelpPlus;
import com.demo.mapper.DbLockMapper;
import com.demo.pojo.DbLock;
import com.demo.service.IDbLockService;
import com.demo.service.criteria.DbLockQueryCriteria;
import com.demo.service.dto.DbLockDto;
import com.demo.service.vo.MyConverterUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author author
 * @since 2024-10-29
 */
@Service
public class DbLockServiceImpl extends ServiceImpl<DbLockMapper, DbLock> implements IDbLockService {

    private final static  int WAIT_TIMES = 500;
    
    @Autowired
    private DbLockMapper dbLockMapper;

    @Override
    public <T> T tryLock(String key, Callable<T> callback) throws Exception {
        return tryLock(key, callback, TimeUnit.SECONDS.toMillis(5));
    }

    @Override
    public <T> T tryLock(String key, Callable<T> callback, long waitTime, TimeUnit unit) throws Exception {
        return tryLock(key, callback, unit.toMillis(waitTime));
    }

    private <T> T tryLock(String key, Callable<T> callback, long waitMillis) throws Exception {

        long startMillis = System.currentTimeMillis();
        DbLockDto dbLockDto = null;

        try {
            dbLockDto = lock(key);
            return callback.call();
        } catch (SQLException e) {
            try {
                TimeUnit.MILLISECONDS.sleep(WAIT_TIMES);
            } catch (InterruptedException ignore) {
            }

            long restartTime = System.currentTimeMillis() - startMillis - waitMillis;
            if (restartTime < 0) {
                throw new WaitingLockDBException("服务繁忙，请重新操作");
            } else {
                return tryLock(key, callback, restartTime);
            }

        } finally {
            if (dbLockDto != null) {
                release(dbLockDto);
            }
        }

    }

    private DbLockDto lock(String key) {
        final DbLockDto dbLockDto = DbLockDto.builder().key(key).build();
        try {
            boolean isSuccess = save(dbLockDto);
            if (!isSuccess) {
                throw new WaitingLockDBException("数据库锁插入异常");
            }
            return dbLockDto;
        } catch (Exception e) {
            final DbLockDto lockData = queryOne(DbLockQueryCriteria.builder().key(key).build());
            if (lockData != null && lockData.isExpired()) {
                release(lockData);
            }

            throw new WaitingLockDBException("数据库锁插入异常");
        }

    }

    private boolean save(DbLockDto dbLockDto) {
        final DbLock dbLock = ConverterUtil.convert(dbLockDto, DbLock.class);
        final boolean save = super.save(dbLock);
        dbLockDto.setId(dbLock.getId());
        return save;
    }

    private void release(DbLockDto dbLockDto) {
        removeById(dbLockDto.getId());
    }

    @Override
    public void releaseAll() {
        List<DbLockDto> dbLockDtos = queryAll(null);
        for (DbLockDto each : dbLockDtos) {
            release(each);
        }
    }

    @Override
    public DbLockDto saveOrUpdate(DbLockDto dbLockDto) {
        final DbLock DbLock = ConverterUtil.convert(dbLockDto, DbLock.class);
        if (ObjectUtil.isNull(DbLock.getId())) {
            baseMapper.insert(DbLock);
        } else {
            baseMapper.updateById(DbLock);
        }
        return convertDto(DbLock, null);
    }

    @Override
    public List<DbLockDto> queryAll(DbLockQueryCriteria criteria) {
        return convertDto(baseMapper.selectList(query(criteria)), criteria);
    }

    @Override
    public PageResult<DbLockDto> queryAll(DbLockQueryCriteria criteria, Pageable pageable) {
        final int page;
        final int limit;
        if (criteria.getPage() != null && criteria.getLimit() != null) {
            page = criteria.getPage();
            limit = criteria.getLimit();
        } else {
            page = pageable.getPageNumber();
            limit = pageable.getPageSize();
        }
        final IPage<DbLock> pageList = baseMapper.selectPage(new Page<>(page, limit), query(criteria));

        return new PageResult<DbLockDto>(pageList.getTotal(), convertDto(pageList.getRecords(), criteria));
    }

    @Override
    public DbLockDto queryById(Long id) {
        final List<DbLockDto> records = queryAll(DbLockQueryCriteria.builder().id(id).build());
        if (!records.isEmpty()) {
            return records.get(0);
        }
        return null;
    }

    @Override
    public DbLockDto queryOne(DbLockQueryCriteria criteria) {
        final List<DbLockDto> records = queryAll(criteria);
        if (!records.isEmpty()) {
            return records.get(0);
        }
        return null;
    }

    @Override
    public Long count(DbLockQueryCriteria criteria) {
        return baseMapper.selectCount(query(criteria));
    }

    @Override
    public DbLock getOne(DbLockQueryCriteria criteria) {
        final List<DbLock> records = getAll(criteria);
        if (!records.isEmpty()) {
            return records.get(0);
        }
        return null;
    }

    @Override
    public List<DbLock> getAll(DbLockQueryCriteria criteria) {
        return baseMapper.selectList(query(criteria));
    }

    @Override
    public int deleteId(Long id) {
        final DbLockDto DbLockDto = queryById(id);
        delete(DbLockDto);
        return 1;
    }

    @Override
    @Transactional
    public int deleteByMore(DbLockQueryCriteria criteria) {
        final List<DbLockDto> DbLockDtos = queryAll(criteria);
        for (DbLockDto each: DbLockDtos) {
            delete(each);
        }
        return DbLockDtos.size();
    }

    private void delete(DbLockDto dbLockDto) {
        baseMapper.deleteById(dbLockDto.getId());
    }

    public List<DbLock> testXml() {

        return dbLockMapper.selectAllData();
    }

    /**
     * 后续所有的sql逻辑都在这里处理
     */
    private QueryWrapper<DbLock> query(DbLockQueryCriteria criteria) {
        final QueryWrapper<DbLock> wrapper = QueryHelpPlus.getPredicate(DbLock.class, criteria);
        if (ObjectUtil.isNull(criteria)) {
            return wrapper;
        }

        return wrapper;
    }

    private DbLockDto convertDto(DbLock record, DbLockQueryCriteria criteria) {
        return MyConverterUtils.INSTANCE.toDbLockDto(record);
    }

    private List<DbLockDto> convertDto(List<DbLock> records, DbLockQueryCriteria criteria) {

        final List<DbLockDto> result = new ArrayList<>();

        for (DbLock each : records) {
            result.add(convertDto(each, criteria));
        }

        return result;
    }
}
